官方文件一開始的 What is Jenkins?
一節中是這麼介紹 Jenkins 的:
Jenkins is a self-contained, open source automation server which can be used to automate all sorts of tasks related to building, testing, and delivering or deploying software.
Jenkins 是個(提供)自動化的伺服器,可用來將各種關於 building、測試、發佈、部署軟體的工作自動化。
我自己覺得學習這類提供「自動化」的工具,例如 Ansible、Jenkins,有一個困難的地方在於「要把什麼工作自動化」。我曾有一份工作的內容是維護 PHP 開發的網站,當時只有一台開發機和一台對外服務的機器,因為是完完全全用 plain PHP 開發的,沒有框架也沒有樣板,甚至沒有進版控系統。每次修了 bug 或是開發了新功能之後,就是手動稍微測試一下,若要將修改的檔案部署到對外服務機器的流程,就是在開發機把修改過的檔案和一支 script 壓成一個 tar 檔,然後把 tar 檔丟到服務機器,解開之後執行 script 把修改過後的檔案複製到相對應的位置。在整個開發到部署的過程中,基本上沒有什麼能夠自動化的空間,而且會以為這整個就是一般應用服務系統的流程。
後來看的東西比較多了,才知道這個過程還有很多的可能性,會根據所用的語言框架、技術複雜度、開發團隊的人數規模、公司的文化習慣、應用程式服務使用者上線的量、開發團隊與維運團隊的友好程度……,各種因素都有可能影響開發到建置的過程中會出現那些步驟。當然也不是說採用 A 流程就一定比 B 流程好,但當整個部署流程的步驟愈複雜、參與的人數愈多,就愈需要自動化,因為它可以降低對人力的依賴,並減少出錯的機率。但剛才說採用 A 流程不一定就比 B 流程好,那麼有自動化一定會比自動化好嗎?雖然我很想說是,但實際上也是看狀況而定,之前有遇到上級指示說自動化後,沒有了人為判斷會有風險,但就是因為不想要有人為判斷所以才會想要自動化。總之我的重點是,自動化工具的使用並不困難,難的是要瞭解原來的流程是什麼,或者設計出一個更好的流程,有了這個流程,才能去思考如何自動化。
Jenkins 本身是一個伺服器應用程式,有很多方式可以使用,不一定需要安裝的動作,例如已安裝 Java,可以直接執行 Jenkins 的 jar 檔來跑這個伺服器。不過既然剛學完 Docker,Jenkins 也提供了映像檔,就用 Docerk 跑 Jenkins 吧。
使用 Docker 執行 Jenkins 的方式非常簡單,只要執行下列指令就可以了:
$ docker run -u root --rm -d \
-p 8080:8080 \
-v jenkins-data:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$HOME":/home \
jenkinsci/blueocean
以上指令的參數大部分都很熟悉了,-u root
表示以 root 身份執行,--rm
表示容器停止後即刪除,-d
表示以 detached 模行運行,接下來 -p
有一組 port 的對應,-v 則是 volume,把容器中 Jenkins 的 /var/jenkins_home
掛載到 Docker 的 volume,這裡 volume 會自動建立,或者可以用 docker volume create jenkins-data
自行建立。下一個 -v
是把容器內的 Docker socket 和本機的 Docker socket 綁在一起,這樣容器內的 Docker 就可以用本機的 runtime 來執行。再下一個 -v
是待會實作時會用到的專案存放位置,直接掛載到容器內,就可以直接在 Jenkins 中取得,不需額外的上傳動作。最後的 jenkinsci/blueocean
是映像檔的位置。
執行後就可以打開瀏覽器利用 8080 port 訪問 Jenkins 了,先來做一些相關設置。第一個畫面要求輸入 Administrator 密碼,可以藉著從 Jenkins 的 log 得到,或者是至以上畫面所說的檔案去取得。要查看 log 的指令為 docker logs <container-name>
,其中會有類似下面的內容:
*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
68d1df1663bf4850afaa90db20c24661
This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
*************************************************************
將密碼輸入後按下下方 Continue 按鈕,下一個畫面是安裝 plugin,選擇左邊的預設安裝即可。安裝好後會要求輸入一組帳密作為管理者之用,填好後安裝就完成了,開始 Jenkins 的第一個畫面大概是這樣:
首先看一下 Guided Tour 的內容,請參考 http://jenkins.io/doc/pipeline/tour/hello-world/ ,這裡先對 Jenkins Pipeline 作個定義,它說 Jenkins 的 pipeline 是一組 plugin,可支援實作以及將持續整合 pipeline 整合至 Jenkins 中,而持續整合 pipeline 是一個自動化的表達式 (automated expression),表現了軟體由版本控制系統到用戶的全部過程。Jenkins pipeline 提供了許多用於整個交付 pipeline 的工具組合,可用來表示各種簡單至複雜的過程,並將它們以程式碼的方式呈現。Jenkins pipeline 的定義通常會寫成一個文字檔,取名為 Jenkinsfile
。Pipeline 中文一般會翻成管線,大概是「有順序性的階段或步驟的總合」類似這樣的意思。
實作的部分則由 Tutorial 開始, Guided Tour 的內容比較接近概念性的說明。Tutorial 中的範例是真的可以操作的,就選擇其中一個開始吧。這裡選擇的是 Build a Node.js and React app with npm
。
首先將專案程式碼 clone 到本機,指令是 git clone https://github.com/jenkins-docs/simple-node-js-react-npm-app
。請把它 clone 到自己家目錄底下的位置,這樣先前容器的掛載點才看的到這些檔案。
瞭解流程是自動化的基礎,在使用 Jenkins 進行自動化前,先來瞭解一下這個 app 到底在做什麼,以及整個 pipeline 過程會做什麼事。因為待會 Jenkins 會使用 node:6-alpine
這個映像檔來跑整個流程,所以這裡也用這個映像檔來執行這個 app。請先執行下列指令來跑這個映像檔:
$ docker run --rm -it -p 3000:3000 -v "$HOME":/home node:6-alpine sh
/ #
請 cd 到剛才 clone 專案的位置,假設 clone 到本機的 $HOME/jenkins/simple-node-js-react-npm-app
,在容器中會是 /home/jenkins/simple-node-js-react-npm-app
。首先執行 npm install
指令,它會安裝此 app 所需要的模組,安裝完成後在專案的根目錄會產生 node_modoules
目錄,裡頭放的就是剛才安裝的模組。
接著進行測試,指令是 npm test
,出現如下畫面後鍵入 q 以離開監看模式,回到命令提示字元。
PASS src/App.test.js
✓ renders without crashing (25ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.758s, estimated 6s
Ran all test suites related to changed files.
Watch Usage
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press q to quit watch mode.
› Press Enter to trigger a test run.
console.error node_modules/scheduler/cjs/scheduler.development.js:479
This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills
console.error node_modules/scheduler/cjs/scheduler.development.js:482
This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills
最後一個步驟是建置 production 環境的程式碼,指令是 npm run build
,它會建立一個 build
目錄,並將編譯好的、效能較高的程式碼放到該目錄中以作為生產環境之用。建置置完成後會出現下面畫面。
The project was built assuming it is hosted at the server root.
To override this, specify the homepage in your package.json.
For example, add this to build it for GitHub Pages:
"homepage" : "http://myname.github.io/myapp",
The build folder is ready to be deployed.
You may serve it with a static server:
npm install -g serve
serve -s build
雖然編譯好的程式碼放在 build 目錄中,不過這裡的教學並沒有特別說明如何將建置好的程式碼部署到生產環境,原則上應該會有一或多台正式提供服務的網頁伺服器,然後把編譯好的程式丟過去。這裡為了要看看這個 app 服務究竟是什麼樣子,使用一個開發環境測試用的伺服器來 "serve" 這個應用程式,指令是 npm start
。它吃的是 src
目錄的內容,不是剛才建置產生的 build
目錄的內容。npm start
執行後會出現下面畫面:
Compiled successfully!
You can now view my-app in the browser.
Local: http://localhost:3000/
On Your Network: http://172.17.0.4:3000/
Note that the development build is not optimized.
To create a production build, use npm run build.
這個時候就可以用本機瀏覽器拜訪 http://localhost:3000,可看到應用程式的主畫面如下。請注意這裡本機的 3000 port 可以連到服務是因為前面執行容器時,有指定一個容器 3000 port 對本機 3000 port 的對應,所以連接的是本機的 3000 port 而不是直接對容器的 3000 port。可以嘗試修改 src/app.js
中 render()
裡 return 回應的字串,看看應用程式網頁的內容是否隨之改變。試驗結束後以 ctrl + c 結束 node 伺服器,並離開容器。
以上就是這一整個應用程式專案要做的事,複習一下有那些步驟:
接下來就開始用 Jenkins 來自動化上面的步驟。因為剛才的操作產生很多檔案,在開始之前,先把剛才本機的專案砍掉再 clone 一次。另外下面很多步驟都是在網頁介面操作的,所以下面的說明會分點描述,看起來比較清楚。
回到 Jenkins 網頁介面,按下「建立新工作」。
輸入專案名稱,請輸入自己想要的名稱,例如 simple-node-js-react-npm-app
,並在下方選擇一個專案的類型,這裡選擇第二個 Pipeline,接著按下 OK。
會出現一個上方有四個 tab 的設置畫面,請直接捲到最下方,或選取第四個 tab Pipeline。
這一個頁面依續填入下列資訊:
$HOME/jenkins/simple-node-js-react-npm-app
,會變成 /home/jenkins/simple-node-js-react-npm-app
。接著在專案的根目錄中建立名為 Jenkinsfile
的文字檔案,內容如下:
pipeline {
agent {
docker {
image 'node:6-alpine'
args '-p 3000:3000'
}
}
stages {
stage('Build') {
steps {
sh 'npm install'
}
}
}
}
Jenkinsfile
也是以宣告 (declarative) 的方式來撰寫,這個語法,嗯,不是 YAML 也不是 JSON 也不是 INI-like,總之一開始要用 pipeline 來宣告。agent 是指 pipeline 執行時要用的環境,這裡是直接定義在 pipeline 之下,表示後面所有的階段都要以這裡指定的環境執行。這裡的 agent 指定 Docker,有它使用的映像檔及執行時的參數,要揭露 3000 port 的原因,剛才已示範過是待會要能夠從外存取此應用程式。在 stages 的部分,先新增一個 Build srage,它要做的事是執行 npm install
這個 shell 指令。
接著將這個檔案在本機 commit 後,回到 Jenkins web 頁面,應該會進入此專案的畫面。
npm install
。第二個步驟會執行一些時間,此時若在本機執行 docker container ls
,會出現跑 node:6-alpine
的 Docker 容器。每個步驟可以展開,看到指令執行時的輸出情形。按下右上角的 X 可以回到 blue ocean 頁面。到這裡就完成了第一個階段,在 Jenkinsfile
將它定義為 build。當然這裡要怎麼定義各個階段、各個階段要做些什麼,是視自己的實際需要而定的。接下來要加入第二個階段,是測試階段,請將 Jenkinsfile 修改為以下內容後將其 commit。
pipeline {
agent {
docker {
image 'node:6-alpine'
args '-p 3000:3000'
}
}
environment {
CI = 'true'
}
stages {
stage('Build') {
steps {
sh 'npm install'
}
}
stage('Test') {
steps {
sh './jenkins/scripts/test.sh'
}
}
}
}
這裡加上了 environment
區段,它用來定義環境變數,這裡設置了一個變數 CI
,其值為 true。前面 npm test
最後是進入監看模式,要按 q 才能離開,這裡指示 CI
為 true
,可以進入非監看模式,讓測試過程自動結束。在 stages 這個區段,多加了一個 stage 為 Test,它會執行 test.sh
,請自己查看文件內容,其實它執行的就是 npm test
這個指令。
commit Jenkinsfile
後回到 Jenkiins 網頁 blue ocean,按下運行,並從右下角的打開進入詳細流程頁面。這次執行仍會從頭執行,但 npm install 速度會快的多(之前已安裝過了),畫面中間偏上的流程圖會多出一個 Test 的圓圈,點擊不同階段的圓圈可以查看各階段的執行步驟及輸出訊息。
完成了 Test 階段後,在 Jenkinsfile
中加入最後一個階段 Deliver,請在 Jenkinsfile 的 Test stage 後加入以下片段:
stage('Deliver') {
steps {
sh './jenkins/scripts/deliver.sh'
input message: 'Finished using the web site? (Click "Proceed" to continue)'
sh './jenkins/scripts/kill.sh'
}
}
這裡定義了 Deliver 交付階段,第一個指令跑 deliver.sh
這支 script,實際上是執行 npm run build
及 npm start
,然後下一個 input message
,它的作用是和使用者互動,決定是否繼續執行或結束,此時可以用 http://localhost:3000 來訪問以 npm start
所 serve 的應用程式。若按下繼續,就會執行 kill.sh
來作清理並完成這次的 pipeline 執行,也結束了這一個範例。
今天首先準備了 Jenkins 的操作環境,和以往直接安裝軟體或在虛擬機器中安裝的方式不同,使用 Docker 作為運行 Jenkins 伺服器的環境,接下來透過一個簡單幾個步驟的應用程式建置測試部署的過程,練習如何利用 Jenkinsfile
建立一個 pipeline 來自動化任務。今天就先到這裡,明天再繼續。